// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using Microsoft.EntityFrameworkCore.Metadata;

namespace Microsoft.EntityFrameworkCore.Storage
{
    public abstract class RelationalTypeMapperTestBase
    {
        protected IMutableEntityType CreateEntityType<TEntity>()
        {
            var builder = CreateModelBuilder();

            builder.Entity<MyType>().Property(e => e.Id).HasColumnType("money");
            builder.Entity<MyRelatedType1>().Property(e => e.Id).HasMaxLength(200).IsFixedLength();
            builder.Entity<MyRelatedType1>().Property(e => e.Relationship2Id).HasColumnType("dec(6,1)");
            builder.Entity<MyRelatedType2>().Property(e => e.Id).HasMaxLength(100).IsFixedLength();
            builder.Entity<MyRelatedType2>().Property(e => e.Relationship2Id).HasMaxLength(787);
            builder.Entity<MyRelatedType3>().Property(e => e.Id).IsUnicode(false);
            builder.Entity<MyRelatedType3>().Property(e => e.Relationship2Id).HasMaxLength(767);
            builder.Entity<MyRelatedType4>().Property(e => e.Relationship2Id).IsUnicode();
            builder.Entity<MyPrecisionType>().Property(e => e.PrecisionOnly).HasPrecision(16);
            builder.Entity<MyPrecisionType>().Property(e => e.PrecisionAndScale).HasPrecision(18, 7);
            builder.Entity<MyTypeWithIndexAttribute>();

            return builder.Model.FindEntityType(typeof(TEntity));
        }

        protected IModel CreateModel() => CreateEntityType<MyType>().Model.FinalizeModel();

        protected RelationalTypeMapping GetTypeMapping(
            Type propertyType,
            bool? nullable = null,
            int? maxLength = null,
            int? precision = null,
            int? scale = null,
            Type providerType = null,
            bool? unicode = null,
            bool? fixedLength = null,
            string storeTypeName = null,
            bool useConfiguration = false)
        {
            if (useConfiguration)
            {
                var model = CreateModelBuilder(c =>
                {
                    var scalarBuilder = c.DefaultTypeMapping(propertyType);

                    if (maxLength.HasValue)
                    {
                        scalarBuilder.HasMaxLength(maxLength.Value);
                    }

                    if (precision.HasValue)
                    {
                        if (scale.HasValue)
                        {
                            scalarBuilder.HasPrecision(precision.Value, scale.Value);
                        }
                        else
                        {
                            scalarBuilder.HasPrecision(precision.Value);
                        }
                    }

                    if (providerType != null)
                    {
                        scalarBuilder.HasConversion(providerType);
                    }

                    if (unicode.HasValue)
                    {
                        scalarBuilder.IsUnicode(unicode.Value);
                    }

                    if (fixedLength.HasValue)
                    {
                        scalarBuilder.IsFixedLength(fixedLength.Value);
                    }

                    if (storeTypeName != null)
                    {
                        scalarBuilder.HasColumnType(storeTypeName);
                    }
                }).FinalizeModel();

                return CreateRelationalTypeMappingSource().GetMapping(propertyType, model);
            }
            else
            {
                var modelBuilder = CreateModelBuilder();
                var entityType = modelBuilder.Entity<MyType>();
                entityType.Property(e => e.Id);
                var property = entityType.Property(propertyType, "MyProp").Metadata;

                if (nullable.HasValue)
                {
                    property.IsNullable = nullable.Value;
                }

                if (maxLength.HasValue)
                {
                    property.SetMaxLength(maxLength);
                }

                if (precision.HasValue)
                {
                    property.SetPrecision(precision);
                }

                if (scale.HasValue)
                {
                    property.SetScale(scale);
                }

                if (providerType != null)
                {
                    property.SetProviderClrType(providerType);
                }

                if (unicode.HasValue)
                {
                    property.SetIsUnicode(unicode);
                }

                if (fixedLength.HasValue)
                {
                    property.SetIsFixedLength(fixedLength);
                }

                if (storeTypeName != null)
                {
                    property.SetColumnType(storeTypeName);
                }

                var model = modelBuilder.Model.FinalizeModel();
                return CreateRelationalTypeMappingSource().GetMapping(model.FindEntityType(typeof(MyType)).FindProperty(property.Name));
            }
        }

        protected abstract ModelBuilder CreateModelBuilder(Action<ModelConfigurationBuilder> configure = null);
        protected abstract IRelationalTypeMappingSource CreateRelationalTypeMappingSource();

        protected class MyType
        {
            public decimal Id { get; set; }
        }

        protected class MyPrecisionType
        {
            public decimal Id { get; set; }
            public decimal PrecisionOnly { get; set; }
            public decimal PrecisionAndScale { get; set; }
        }

        protected class MyRelatedType1
        {
            public string Id { get; set; }

            public decimal Relationship1Id { get; set; }
            public MyType Relationship1 { get; set; }

            public decimal Relationship2Id { get; set; }
            public MyType Relationship2 { get; set; }
        }

        protected class MyRelatedType2
        {
            public byte[] Id { get; set; }

            public string Relationship1Id { get; set; }
            public MyRelatedType1 Relationship1 { get; set; }

            public string Relationship2Id { get; set; }
            public MyRelatedType1 Relationship2 { get; set; }
        }

        protected class MyRelatedType3
        {
            public string Id { get; set; }

            public byte[] Relationship1Id { get; set; }
            public MyRelatedType2 Relationship1 { get; set; }

            public byte[] Relationship2Id { get; set; }
            public MyRelatedType2 Relationship2 { get; set; }
        }

        protected class MyRelatedType4
        {
            public string Id { get; set; }

            public string Relationship1Id { get; set; }
            public MyRelatedType3 Relationship1 { get; set; }

            public string Relationship2Id { get; set; }
            public MyRelatedType3 Relationship2 { get; set; }
        }

        [Index(nameof(Name))]
        protected class MyTypeWithIndexAttribute
        {
            public int Id { get; set; }
            public string Name { get; set; }
        }
    }
}
